﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Xml;
using System.ServiceModel.Channels;
using Obra.Common;

namespace Obra.SmugMug.Api
{
    // TODO: (FIXME) duplicate code with SmugMugClientMessageFormatter.cs .

    /// <summary>
    /// Client message formatter - augment the incoming Message, and make the
    /// downstream XML parsing easier.
    /// </summary>
    /// <remarks>
    /// <para>
    /// SmugMug XML response are wrapped in a rsp envelope, and include an
    /// element containing the method.  If the response is a fail the message 
    /// envelope contains an err element with a message of why it failed.
    /// </para>
    /// <para>
    /// I want to do two things.  If the response is fail, generate an 
    /// appropriate exception with the failure message.  If the response is
    /// a success I want to discard the enclosing rsp envelope, and discard
    /// the superfolous method element.
    /// </para>
    /// <para>
    /// This class derives from Attribute, so it can be easily used to annotate
    /// individual Operations (methods)
    ///     
    ///   [OperationContract]
    ///   [SmugMugClientMessageFormatter]
    ///   [WebGet(UriTemplate = UriTemplates.Session.LoginWithPassword)]
    ///   public LoginWithPassword LoginWithPassword(...);
    ///   
    /// </para>
    /// </remarks>
    internal sealed class MessageFormatter:
        Attribute,
        IClientMessageFormatter,
        IOperationBehavior
    {
        /// <summary>
        /// Advance the XmlDictionaryReader to the specified element name.
        /// </summary>
        /// <param name="xdr">
        /// XmlDictionaryReader to advance.
        /// </param>
        /// <param name="elementName">
        /// The element name to move to.
        /// </param>
        private static void MoveToElement(
            XmlDictionaryReader xdr,
            string elementName)
        {
            while (xdr.Name != elementName)
            {
                xdr.Read();
            }
        }

        /// <summary>
        /// Move to next non-empty element.
        /// </summary>
        /// <param name="xdr">
        /// XmlDictionaryReader to advance.
        /// </param>
        private static void MoveToNextElement(
            XmlDictionaryReader xdr)
        {
            while (xdr.Read() && (String.IsNullOrEmpty(xdr.Name) || !xdr.IsStartElement())) 
                ;
        }

        /// <summary>
        /// Check the REST response from smugmug.com for an error, and if
        /// there is one, throw an exception.
        /// </summary>
        /// <param name="xdr">
        /// The XmlDictionaryReader to interrogate for an error.
        /// </param>
        private static void CheckForBadResponse(
            XmlDictionaryReader xdr)
        {
            xdr.MoveToStartElement();
            if (xdr.GetAttribute("stat") != "ok")
            {
                MessageFormatter.MoveToElement(xdr, "err");
                
                Log.Error(
                    xdr.GetAttribute("msg"),
                    LogCategory.REST,
                    LogCategory.SmugMug);

                throw new SmugMugException(
                    xdr.GetAttribute("msg"));
            }
        }

        /// <summary>
        /// The original client message formatter is still used after the rsp
        /// envelope is removed.
        /// </summary>
        private IClientMessageFormatter OriginalClientMessageFormatter
        {
            get;
            set;
        }

        #region IOperationBehavior Members

        /// <summary>
        /// Nothing to do.
        /// </summary>
        public void AddBindingParameters(
            OperationDescription operationDescription,
            BindingParameterCollection bindingParameters)
        {
        }

        /// <summary>
        /// Swap out the default formatter with our formatter.
        /// </summary>
        public void ApplyClientBehavior(
            OperationDescription operationDescription,
            ClientOperation clientOperation)
        {
            this.OriginalClientMessageFormatter = clientOperation.Formatter;
            clientOperation.Formatter = this;
        }

        /// <summary>
        /// Nothing to do.
        /// </summary>
        public void ApplyDispatchBehavior(
            OperationDescription operationDescription,
            DispatchOperation dispatchOperation)
        {
        }

        /// <summary>
        /// Nothing to do.
        /// </summary>
        public void Validate(
            OperationDescription operationDescription)
        {
        }

        #endregion

        #region IClientMessageFormatter Members

        /// <summary>
        /// Deserialize the incoming XML request.
        /// </summary>
        public object DeserializeReply(
            Message message,
            object[] parameters)
        {
            XmlDictionaryReader xdr = message.GetReaderAtBodyContents();
            xdr.MoveToStartElement();

            MessageFormatter.CheckForBadResponse(xdr);
            MessageFormatter.MoveToElement(xdr, "method");
            MessageFormatter.MoveToNextElement(xdr);

            if (String.IsNullOrEmpty(xdr.Name))
            {
                return null;
            }

            Message formattedMessage = Message.CreateMessage(message.Version, "", xdr);

            return this.OriginalClientMessageFormatter.DeserializeReply(
                formattedMessage,
                parameters);
        }

        /// <summary>
        /// Serialize the outgoing request.
        /// </summary>
        public Message SerializeRequest(
            MessageVersion messageVersion,
            object[] parameters)
        {
            return this.OriginalClientMessageFormatter.SerializeRequest(
                messageVersion,
                parameters);
        }

        #endregion
    }
}
